/* compiler.js */
import Watcher from "./watcher.js";
export default class Compiler {
  constructor(el, data) {
    // 拿到 响应式数据
    this.data = data;
    // 拿到 el
    this.el = el;
    // 编译模板
    this.compile(this.el);
  }
  // 编译模板
  compile(el) {
    // 获取子节点 如果使用 forEach遍历就把伪数组转为真的数组
    let childNodes = [...el.childNodes].filter((node) => {
      // 过滤掉换行和空的文本节点
      return !(node.nodeType == "3" && node.nodeName == "#text" && /\s/.test(node.nodeValue));
    });
    childNodes.forEach((node) => {
      // 根据不同的节点类型进行编译
      // 文本类型的节点
      if (this.isTextNode(node)) {
        // 编译文本节点
        this.compileText(node);
      } else if (this.isElementNode(node)) {
        //元素节点
        this.compileElement(node);
      }
      // 判断是否还存在子节点考虑递归
      if (node.childNodes && node.childNodes.length) {
        // 继续递归编译模板
        this.compile(node);
      }
    });
  }
  // 编译文本节点(简单的实现)
  compileText(node) {
    // 核心思想利用把正则表达式把{{}}去掉找到里面的变量
    // 再去Vue找这个变量赋值给node.textContent
    let reg = /\{\{(.+?)\}\}/;
    // 获取节点的文本内容
    let val = node.textContent;
    // 判断是否有 {{}}
    if (reg.test(val)) {
      // 获取分组一  也就是 {{}} 里面的内容 去除前后空格
      let key = RegExp.$1.trim();
      let value = getNestedProperty(this.data, key);
      // 进行替换再赋值给node
      node.textContent = val.replace(reg, value);
      // 创建观察者
      new Watcher(this.data, key, (newValue) => {
        node.textContent = newValue;
      });
    }
  }
  // 编译元素节点这里只处理指令
  compileElement(node) {
    // 获取到元素节点上面的所有属性进行遍历
    [...node.attributes].forEach((attr) => {
      // 获取属性名
      let attrName = attr.name;
      // 判断是否是 v- 开头的指令
      if (this.isDirective(attrName)) {
        // 除去 v- 方便操作
        attrName = attrName.substr(2);
        // 获取 指令的值就是  v-text = "msg"  中msg
        // msg 作为 key 去Vue 找这个变量
        let key = attr.value.trim();
        // 找到指令后执行指令操作
        // 指令操作 执行指令方法
        // vue指令很多为了避免大量个 if判断这里就写个 uapdate 方法
        this.update(node, key, attrName);
      }
    });
  }
  // 添加指令方法 并且执行
  update(node, key, attrName) {
    // 比如添加 textUpdater 就是用来处理 v-text 方法
    // 我们应该就内置一个 textUpdater 方法进行调用
    // 加个后缀加什么无所谓但是要定义相应的方法
    let updateFn = this[attrName + "Updater"];
    // 如果存在这个内置方法 就可以调用了
    updateFn && updateFn.call(this, node, key, getNestedProperty(this.data, key));
  }
  // 提前写好 相应的指定方法比如这个 v-text
  // 使用的时候 和 Vue 的一样
  textUpdater(node, key, value) {
    node.textContent = value;
    // 创建观察者
    new Watcher(this.data, key, (newValue) => {
      node.textContent = newValue;
    });
  }
  // v-model
  modelUpdater(node, key, value) {
    node.value = value;
    // 创建观察者
    new Watcher(this.data, key, (newValue) => {
      node.value = newValue;
    });
    // 这里实现双向绑定
    node.addEventListener("input", () => {
      setNestedProperty(this.data, key, node.value);
    });
  }

  // 判断元素的属性是否是 vue 指令
  isDirective(attr) {
    return attr.startsWith("v-");
  }
  // 判断是否是元素节点
  isElementNode(node) {
    return node.nodeType === 1;
  }
  // 判断是否是 文本 节点
  isTextNode(node) {
    return node.nodeType === 3;
  }
}

export function getNestedProperty(obj, key) {
  // 将key按照点号分隔成数组
  let keys = key.split(".");

  // 遍历数组，逐层深入对象
  return keys.reduce((acc, cur) => {
    // 如果acc不是对象，或者对象中不含有cur属性，返回undefined
    if (acc && acc.hasOwnProperty(cur)) {
      return acc[cur];
    } else {
      return undefined;
    }
  }, obj);
}

function setNestedProperty(obj, key, value) {
  // 将key按照点号分隔成数组
  let keys = key.split(".");
  // 最后一个属性名
  let lastKey = keys.pop();
  // 遍历数组，逐层深入对象
  let nested = keys.reduce((acc, cur) => {
    if (!acc[cur]) {
      acc[cur] = {}; // 如果不存在该属性则创建空对象
    }
    return acc[cur];
  }, obj);

  // 设置最后一级的属性值
  nested[lastKey] = value;
}
